﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace MissingLinq.Linq2Management.Context
{
	/// <summary>
	/// Expression visitor that converts and expression tree into a Windows Management Query (WQL)
	/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa394606%28v=vs.85%29.aspx
	/// </summary>
	internal class QueryTranslator : ExpressionVisitor
	{
		internal QueryTranslator(String className)
		{
			_className = className;
		}

		internal string Translate(Expression expression)
		{
			if (expression == null)
				return "SELECT * FROM " + _className;

			_queryStringBuilder.Clear();
			Visit(expression);
			return _queryStringBuilder.ToString();
		}

		protected override Expression VisitMethodCall(MethodCallExpression m)
		{
			if (m.Method.DeclaringType == typeof(Queryable) && m.Method.Name == "Where")
			{
				_queryStringBuilder.Append("SELECT * FROM " + _className + " WHERE ");
				var lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
				Visit(lambda.Body);
				return m;
			}

			throw new NotSupportedException(String.Format("The method '{0}' is not supported", m.Method.Name));
		}

		protected override Expression VisitUnary(UnaryExpression u)
		{
			switch (u.NodeType)
			{
				case ExpressionType.Not:
					_queryStringBuilder.Append(" NOT ");
					Visit(u.Operand);
					break;

				case ExpressionType.Convert:
					Visit(u.Operand);
					break;

				default:
					throw new NotSupportedException(String.Format("The unary operator '{0}' is not supported", u.NodeType));
			}

			return u;
		}

		protected override Expression VisitBinary(BinaryExpression b)
		{
			_queryStringBuilder.Append("(");

			Visit(b.Left);

			switch (b.NodeType)
			{
				case ExpressionType.And:
				case ExpressionType.AndAlso:
					_queryStringBuilder.Append(" AND ");
					break;

				case ExpressionType.Or:
				case ExpressionType.OrElse:
					_queryStringBuilder.Append(" OR ");
					break;

				case ExpressionType.Equal:
					_queryStringBuilder.Append(" = ");
					break;

				case ExpressionType.NotEqual:
					_queryStringBuilder.Append(" <> ");
					break;

				case ExpressionType.LessThan:
					_queryStringBuilder.Append(" < ");
					break;

				case ExpressionType.LessThanOrEqual:
					_queryStringBuilder.Append(" <= ");
					break;

				case ExpressionType.GreaterThan:
					_queryStringBuilder.Append(" > ");
					break;

				case ExpressionType.GreaterThanOrEqual:
					_queryStringBuilder.Append(" >= ");
					break;

				default:
					throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
			}

			Visit(b.Right);
			_queryStringBuilder.Append(")");
			return b;
		}

		protected override Expression VisitConstant(ConstantExpression c)
		{
			if (c.Value == null)
			{
				_queryStringBuilder.Append("NULL");
			}
			else
			{
				switch (Type.GetTypeCode(c.Value.GetType()))
				{
					case TypeCode.Boolean:
						_queryStringBuilder.Append(((bool)c.Value) ? 1 : 0);
						break;

					case TypeCode.String:
						_queryStringBuilder.Append("'");
						_queryStringBuilder.Append(c.Value);
						_queryStringBuilder.Append("'");
						break;

					case TypeCode.Object:
						if(c.Value.GetType().IsGenericType && c.Value.GetType().GetGenericTypeDefinition() == typeof(ObjectSet<>))
							_queryStringBuilder.Append("SELECT * FROM " + _className);
						else
							throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));
						break;

					default:
						_queryStringBuilder.Append(c.Value);
						break;
				}
			}

			return c;
		}

		protected override Expression VisitMember(MemberExpression m)
		{
			if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
			{
				_queryStringBuilder.Append(m.Member.Name);
				return m;
			}

			throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
		}

		private static Expression StripQuotes(Expression e)
		{
			while (e.NodeType == ExpressionType.Quote)
				e = ((UnaryExpression)e).Operand;

			return e;
		}

		private readonly StringBuilder _queryStringBuilder = new StringBuilder();
		private readonly String _className;
	}
}
